package health_system.health_system.service.impl;

import health_system.health_system.dto.*;
import health_system.health_system.entity.Moment;
import health_system.health_system.entity.MomentComment;
import health_system.health_system.entity.User;
import health_system.health_system.exception.BusinessException;
import health_system.health_system.exception.MomentCommentNotFoundException;
import health_system.health_system.exception.MomentNotFoundException;
import health_system.health_system.exception.ResourceNotFoundException;
import health_system.health_system.repository.MomentCommentRepository;
import health_system.health_system.repository.MomentRepository;
import health_system.health_system.repository.UserRepository;
import health_system.health_system.service.MomentCommentService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class MomentCommentServiceImpl implements MomentCommentService {

    private final MomentCommentRepository commentRepository;
    private final MomentRepository momentRepository;
    private final UserRepository userRepository;

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = "momentComments", allEntries = true),
        @CacheEvict(value = "userComments", allEntries = true),
        @CacheEvict(value = "commentReplies", allEntries = true)
    })
    public MomentCommentDTO createComment(MomentCommentCreateDTO createDTO) {
        log.debug("Creating comment for moment ID: {} by user ID: {}", 
                createDTO.getMomentId(), createDTO.getUserId());
        
        Moment moment = momentRepository.findById(createDTO.getMomentId())
                .orElseThrow(() -> new MomentNotFoundException(createDTO.getMomentId()));
        
        User user = userRepository.findById(createDTO.getUserId())
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", createDTO.getUserId()));
        
        MomentComment comment = new MomentComment();
        comment.setMoment(moment);
        comment.setUser(user);
        comment.setContent(createDTO.getContent());
        
        // Set parent comment if this is a reply
        if (createDTO.getParentId() != null) {
            MomentComment parentComment = commentRepository.findById(createDTO.getParentId())
                    .orElseThrow(() -> new MomentCommentNotFoundException(createDTO.getParentId()));
            
            // Ensure parent comment belongs to the same moment
            if (!parentComment.getMoment().getId().equals(moment.getId())) {
                throw new BusinessException("Parent comment does not belong to the specified moment");
            }
            
            comment.setParent(parentComment);
        }
        
        // Set reply to user if specified
        if (createDTO.getReplyToUserId() != null) {
            User replyToUser = userRepository.findById(createDTO.getReplyToUserId())
                    .orElseThrow(() -> new ResourceNotFoundException("User", "id", createDTO.getReplyToUserId()));
            
            comment.setReplyToUser(replyToUser);
        }
        
        try {
            MomentComment savedComment = commentRepository.save(comment);
            log.info("Successfully created comment with ID: {}", savedComment.getId());
            return MomentCommentDTO.fromEntity(savedComment);
        } catch (DataIntegrityViolationException e) {
            log.error("Failed to create comment due to data integrity violation", e);
            throw new BusinessException("Failed to create comment. Please check your input data.", e);
        }
    }

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = "comment", key = "#id"),
        @CacheEvict(value = "momentComments", allEntries = true),
        @CacheEvict(value = "userComments", allEntries = true),
        @CacheEvict(value = "commentReplies", allEntries = true)
    })
    public MomentCommentDTO updateComment(Long id, MomentCommentUpdateDTO updateDTO) {
        log.debug("Updating comment with ID: {}", id);
        
        MomentComment comment = getCommentEntityById(id);
        
        // Update content if provided
        if (updateDTO.getContent() != null) {
            comment.setContent(updateDTO.getContent());
        }
        
        try {
            MomentComment updatedComment = commentRepository.save(comment);
            log.info("Successfully updated comment with ID: {}", updatedComment.getId());
            return MomentCommentDTO.fromEntity(updatedComment);
        } catch (DataIntegrityViolationException e) {
            log.error("Failed to update comment with ID: {} due to data integrity violation", id, e);
            throw new BusinessException("Failed to update comment. Please check your input data.", e);
        }
    }

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = "comment", key = "#id"),
        @CacheEvict(value = "momentComments", allEntries = true),
        @CacheEvict(value = "userComments", allEntries = true),
        @CacheEvict(value = "commentReplies", allEntries = true)
    })
    public void deleteComment(Long id) {
        log.debug("Deleting comment with ID: {}", id);
        
        if (!commentRepository.existsById(id)) {
            throw new MomentCommentNotFoundException(id);
        }
        
        try {
            commentRepository.deleteById(id);
            log.info("Successfully deleted comment with ID: {}", id);
        } catch (Exception e) {
            log.error("Failed to delete comment with ID: {}", id, e);
            throw new BusinessException("Failed to delete comment with ID: " + id, e);
        }
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "comment", key = "#id")
    public MomentCommentDTO getCommentById(Long id) {
        log.debug("Getting comment by ID: {}", id);
        
        MomentComment comment = getCommentEntityById(id);
        return MomentCommentDTO.fromEntity(comment);
    }
    
    @Override
    @Transactional(readOnly = true)
    public MomentComment getCommentEntityById(Long id) {
        log.debug("Getting comment entity by ID: {}", id);
        
        return commentRepository.findById(id)
                .orElseThrow(() -> new MomentCommentNotFoundException(id));
    }
    
    @Override
    @Transactional(readOnly = true)
    public Optional<MomentComment> findCommentEntityById(Long id) {
        log.debug("Finding comment entity by ID: {}", id);
        
        return commentRepository.findById(id);
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentComments", key = "#momentId")
    public List<MomentCommentDTO> getAllCommentsForMoment(Long momentId) {
        log.debug("Getting all comments for moment ID: {}", momentId);
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        List<MomentComment> comments = commentRepository.findByMomentOrderByCreatedAtAsc(moment);
        
        if (comments.isEmpty()) {
            return Collections.emptyList();
        }
        
        return comments.stream()
                .map(MomentCommentDTO::fromEntity)
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentCommentsPaged", 
              key = "#momentId + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public PageResponseDTO<MomentCommentDTO> getAllCommentsForMoment(Long momentId, Pageable pageable) {
        log.debug("Getting paginated comments for moment ID: {} with page: {}", 
                momentId, pageable.getPageNumber());
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        Page<MomentComment> commentsPage = commentRepository.findByMomentOrderByCreatedAtAsc(moment, pageable);
        
        Page<MomentCommentDTO> dtoPage = commentsPage.map(MomentCommentDTO::fromEntity);
        
        return PageResponseDTO.fromPage(dtoPage);
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentCommentTree", key = "#momentId")
    public MomentCommentTreeDTO getCommentTreeForMoment(Long momentId) {
        log.debug("Getting comment tree for moment ID: {}", momentId);
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        List<MomentComment> allComments = commentRepository.findByMomentOrderByCreatedAtAsc(moment);
        
        if (allComments.isEmpty()) {
            return MomentCommentTreeDTO.builder()
                    .momentId(momentId)
                    .rootComments(Collections.emptyList())
                    .totalCommentCount(0)
                    .build();
        }
        
        // Convert all comments to DTOs
        List<MomentCommentDTO> commentDTOs = allComments.stream()
                .map(MomentCommentDTO::fromEntity)
                .collect(Collectors.toList());
        
        // Build the comment tree
        return MomentCommentTreeDTO.createTreeFromList(momentId, commentDTOs);
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "userComments", key = "#userId")
    public List<MomentCommentDTO> getAllCommentsByUser(Long userId) {
        log.debug("Getting all comments by user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        List<MomentComment> comments = commentRepository.findByUserOrderByCreatedAtDesc(user);
        
        if (comments.isEmpty()) {
            return Collections.emptyList();
        }
        
        return comments.stream()
                .map(MomentCommentDTO::fromEntity)
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "userCommentsPaged", 
              key = "#userId + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public PageResponseDTO<MomentCommentDTO> getAllCommentsByUser(Long userId, Pageable pageable) {
        log.debug("Getting paginated comments by user ID: {} with page: {}", 
                userId, pageable.getPageNumber());
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        Page<MomentComment> commentsPage = commentRepository.findByUserOrderByCreatedAtDesc(user, pageable);
        
        Page<MomentCommentDTO> dtoPage = commentsPage.map(MomentCommentDTO::fromEntity);
        
        return PageResponseDTO.fromPage(dtoPage);
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "userCommentedMoments", key = "#userId")
    public List<MomentDTO> getMomentsCommentedByUser(Long userId) {
        log.debug("Getting moments commented by user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        List<Moment> moments = commentRepository.findMomentsCommentedByUser(user);
        
        if (moments.isEmpty()) {
            return Collections.emptyList();
        }
        
        return moments.stream()
                .map(MomentDTO::fromEntity)
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "userCommentedMomentsPaged", 
              key = "#userId + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public PageResponseDTO<MomentDTO> getMomentsCommentedByUser(Long userId, Pageable pageable) {
        log.debug("Getting paginated moments commented by user ID: {} with page: {}", 
                userId, pageable.getPageNumber());
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        Page<Moment> momentsPage = commentRepository.findMomentsCommentedByUser(user, pageable);
        
        Page<MomentDTO> dtoPage = momentsPage.map(MomentDTO::fromEntity);
        
        return PageResponseDTO.fromPage(dtoPage);
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "commentReplies", key = "#commentId")
    public List<MomentCommentDTO> getRepliesForComment(Long commentId) {
        log.debug("Getting replies for comment ID: {}", commentId);
        
        MomentComment parentComment = getCommentEntityById(commentId);
        
        List<MomentComment> replies = commentRepository.findByParentOrderByCreatedAtAsc(parentComment);
        
        if (replies.isEmpty()) {
            return Collections.emptyList();
        }
        
        return replies.stream()
                .map(MomentCommentDTO::fromEntity)
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentCommentsCount", key = "#momentId")
    public Long countCommentsForMoment(Long momentId) {
        log.debug("Counting comments for moment ID: {}", momentId);
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        return commentRepository.countByMoment(moment);
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "userCommentsCount", key = "#userId")
    public Long countCommentsByUser(Long userId) {
        log.debug("Counting comments by user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        return commentRepository.countByUser(user);
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "commentRepliesCount", key = "#commentId")
    public Long countRepliesForComment(Long commentId) {
        log.debug("Counting replies for comment ID: {}", commentId);
        
        MomentComment parentComment = getCommentEntityById(commentId);
        
        return commentRepository.countByParent(parentComment);
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentCommenters", key = "#momentId")
    public List<UserDTO> getUsersWhoCommentedOnMoment(Long momentId) {
        log.debug("Getting users who commented on moment ID: {}", momentId);
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        List<User> users = commentRepository.findUsersWhoCommentedOnMoment(moment);
        
        if (users.isEmpty()) {
            return Collections.emptyList();
        }
        
        return users.stream()
                .map(UserDTO::fromEntityBasic)
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentCommentsSearch", key = "#momentId + '-' + #searchTerm")
    public List<MomentCommentDTO> searchCommentsInMoment(Long momentId, String searchTerm) {
        log.debug("Searching comments in moment ID: {} with term: {}", momentId, searchTerm);
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        List<MomentComment> comments = commentRepository.searchCommentsInMoment(moment, searchTerm);
        
        if (comments.isEmpty()) {
            return Collections.emptyList();
        }
        
        return comments.stream()
                .map(MomentCommentDTO::fromEntity)
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "commentsWithMostReplies", key = "#momentId + '-limit:' + #limit")
    public List<MomentCommentDTO> getCommentsWithMostReplies(Long momentId, int limit) {
        log.debug("Getting comments with most replies for moment ID: {} with limit: {}", momentId, limit);
        
        Moment moment = momentRepository.findById(momentId)
                .orElseThrow(() -> new MomentNotFoundException(momentId));
        
        List<Object[]> results = commentRepository.findCommentsWithMostReplies(
                moment, PageRequest.of(0, limit));
        
        if (results.isEmpty()) {
            return Collections.emptyList();
        }
        
        // Process results
        return results.stream()
                .map(result -> {
                    MomentComment comment = (MomentComment) result[0];
                    Long replyCount = (Long) result[1];
                    
                    MomentCommentDTO dto = MomentCommentDTO.fromEntity(comment);
                    dto.setReplyCount(replyCount.intValue());
                    
                    return dto;
                })
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "commentsMentioningUser", key = "#userId + '-' + #mention")
    public List<MomentCommentDTO> findCommentsMentioningUser(Long userId, String mention) {
        log.debug("Finding comments mentioning user ID: {} with mention: {}", userId, mention);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        List<MomentComment> comments = commentRepository.findCommentsMentioningUser(user, mention);
        
        if (comments.isEmpty()) {
            return Collections.emptyList();
        }
        
        return comments.stream()
                .map(MomentCommentDTO::fromEntity)
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "friendsCommentedMoments", key = "#userId + '-limit:' + #limit")
    public List<MomentDTO> getRecentlyCommentedMomentsByFriends(Long userId, int limit) {
        log.debug("Getting recently commented moments by friends of user ID: {} with limit: {}", userId, limit);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        List<Moment> moments = commentRepository.findRecentlyCommentedMomentsByFriends(
                user, PageRequest.of(0, limit));
        
        if (moments.isEmpty()) {
            return Collections.emptyList();
        }
        
        return moments.stream()
                .map(MomentDTO::fromEntity)
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "momentsWithMostComments", key = "#userId + '-limit:' + #limit")
    public List<MomentDTO> getMomentsWithMostCommentsVisibleToUser(Long userId, int limit) {
        log.debug("Getting moments with most comments visible to user ID: {} with limit: {}", userId, limit);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        List<Object[]> results = commentRepository.findMomentsWithMostCommentsVisibleToUser(
                user, PageRequest.of(0, limit));
        
        if (results.isEmpty()) {
            return Collections.emptyList();
        }
        
        // Process results
        return results.stream()
                .map(result -> {
                    Moment moment = (Moment) result[0];
                    Long commentCount = (Long) result[1];
                    
                    MomentDTO dto = MomentDTO.fromEntity(moment);
                    dto.setCommentCount(commentCount.intValue());
                    
                    return dto;
                })
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "userCommentStats", key = "#userId")
    public UserCommentStatsDTO getUserCommentStats(Long userId) {
        log.debug("Getting comment statistics for user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId));
        
        // Get all comments by the user
        List<MomentComment> comments = commentRepository.findByUserOrderByCreatedAtDesc(user);
        
        if (comments.isEmpty()) {
            return UserCommentStatsDTO.builder()
                    .userId(userId)
                    .username(user.getUsername())
                    .userAvatarUrl(user.getAvatarUrl())
                    .totalCommentCount(0L)
                    .momentCommentCount(0L)
                    .replyCount(0L)
                    .momentsCommentedCount(0L)
                    .avgCommentsPerMoment(0.0)
                    .build();
        }
        
        // Count total comments
        long totalCommentCount = comments.size();
        
        // Count root comments vs replies
        long replyCount = comments.stream()
                .filter(comment -> comment.getParent() != null)
                .count();
        
        long momentCommentCount = totalCommentCount - replyCount;
        
        // Count unique moments commented on
        Set<Long> uniqueMomentIds = comments.stream()
                .map(comment -> comment.getMoment().getId())
                .collect(Collectors.toSet());
        
        long momentsCommentedCount = uniqueMomentIds.size();
        
        // Calculate average comments per moment
        double avgCommentsPerMoment = momentsCommentedCount > 0 
                ? (double) totalCommentCount / momentsCommentedCount 
                : 0;
        
        // Get last comment date
        LocalDateTime lastCommentDate = comments.isEmpty() ? null : comments.get(0).getCreatedAt();
        
        return UserCommentStatsDTO.builder()
                .userId(userId)
                .username(user.getUsername())
                .userAvatarUrl(user.getAvatarUrl())
                .totalCommentCount(totalCommentCount)
                .momentCommentCount(momentCommentCount)
                .replyCount(replyCount)
                .momentsCommentedCount(momentsCommentedCount)
                .avgCommentsPerMoment(avgCommentsPerMoment)
                .lastCommentDate(lastCommentDate)
                .build();
    }
    
    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "commentsByIds", key = "#commentIds.hashCode()")
    public List<MomentCommentDTO> getCommentsByIds(List<Long> commentIds) {
        log.debug("Getting comments by IDs: {}", commentIds);
        
        if (commentIds == null || commentIds.isEmpty()) {
            return Collections.emptyList();
        }
        
        List<MomentComment> comments = commentRepository.findAllById(commentIds);
        
        if (comments.isEmpty()) {
            return Collections.emptyList();
        }
        
        return comments.stream()
                .map(MomentCommentDTO::fromEntity)
                .collect(Collectors.toList());
    }
} 